<template>
  <div ref="container" class="container">
  </div>
</template>

<script>
import * as THREE from "three"
import Interaction from 'three.interaction/src/interaction/Interaction';
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader"


class Buiding {
  sceneConfig = {
    backgorund: "#113e7a"
  }
  meshParams = {
    light: {
      color: "#3883cc",
      emissive: "#38516b",
    },
    shade: {
      color: "#a6bfd8",
      emissive: "#4b5359",
    },
    metalness: .79, // 金属度
    roughness: .8, // 粗糙度
    hoverColor: "#bc9657",
    hoverEmissive: "#a06804",
  };
  backMeshParams = {
    color: "#c4c4c4",
    metalness: .79,
    emissive: "#747477",
    roughness: .8,
  };
  planeMeshParams = {
    color: "#152b49",
    metalness: 0,
    emissive: "#23619b",
    roughness: 0,
  }
  fogConfig = {
    color: "#113e7a",
    near: 1,
    far: 200
  }

  animationId
  async init({ dom, data, click, mousemove, mouseout }) {
    this.dom = dom
    this.getSize()
    this.createScene()
    this.createCamera()
    // 开启three.interaction
    new Interaction(this.renderer, this.scene, this.camera);
    this.addFog()
    this.addAmbient()
    this.addSpotLight()
    // 加载模型
    await this.createBuilding(data, click, mousemove, mouseout);
    // 背景楼房
    this.loadeBackBuild()

    this.onResize() // 监听屏幕变化
    this.onMouse() // 监听鼠标移动

    // 渲染
    this.animate()
  }
  getSize() {
    this.width = this.dom.clientWidth
    this.height = this.dom.clientHeight
  }
  // 场景
  createScene() {
    this.scene = new THREE.Scene();

    // 创建渲染器对象
    this.renderer = new THREE.WebGLRenderer({ antialias: true });
    this.renderer.setClearColor(this.sceneConfig.backgorund, 1)
    this.renderer.setSize(this.width, this.height); //设置three.js渲染区域的尺寸(像素px)

    // 插入dom节点
    this.dom.appendChild(this.renderer.domElement)
  }
  // 相机
  createCamera() {
    this.camera = new THREE.PerspectiveCamera(75, this.width / this.height, 0.1, 1000);
    this.scene.add(this.camera)
  }
  addFog() {
    // 雾化
    this.scene.fog = new THREE.Fog(this.fogConfig.color, this.fogConfig.near, this.fogConfig.far);
  }

  // 环境光
  addAmbient() {
    const ambient = new THREE.AmbientLight(0xffffff, 1);
    this.scene.add(ambient);
  }
  // 平行光
  addSpotLight() {
    const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
    directionalLight.position.set(0, 90, 0);
    this.scene.add(directionalLight);
  }
  // 屏幕变化
  sizeChangeHandle() {
    this.getSize()
    // 重置渲染器输出画布canvas尺寸
    this.renderer.setSize(this.width, this.height);
    // 全屏情况下：设置观察范围长宽比aspect为窗口宽高比
    this.camera.aspect = this.width / this.height;
    // 渲染器执行render方法的时候会读取相机对象的投影矩阵属性projectionMatrix
    // 但是不会每渲染一帧，就通过相机的属性计算投影矩阵(节约计算资源)
    // 如果相机的一些属性发生了变化，需要执行updateProjectionMatrix ()方法更新相机的投影矩阵
    this.camera.updateProjectionMatrix();
  }

  // 监听屏幕变化
  onResize() {
    // window.addEventListener('resize', this.sizeChangeHandle.bind(this))
    this.observer = new ResizeObserver(this.sizeChangeHandle.bind(this));
    this.observer.observe(this.dom);
  }
  offResize() {
    // window.removeEventListener('resize', this.sizeChangeHandle.bind(this))
    this.observer.disconnect();
  }

  mouseMoveHandle(ev) {
    this.mouseX = ev.pageX;
    this.mouseY = ev.pageY;
  }
  onMouse() {
    this.cameraPosition = this.camera.position.clone()
    this.lastMouseX = this.mouseX = this.cameraPosition.x;
    this.lastMouseY = this.mouseY = this.cameraPosition.y;
    window.addEventListener('mousemove', this.mouseMoveHandle.bind(this));
  }
  offMouse() {
    window.removeEventListener('mousemove', this.mouseMoveHandle.bind(this))
  }

  // 加载楼栋
  async createBuilding(data, click, mousemove, mouseout) {
    this.models = [];

    let count = data.length // 楼栋数量
    const rows = Math.floor(Math.sqrt(count)); // 行
    const columns = Math.ceil(count / rows); // 列
    const spacing = 5; // 间距
    // 加载模型
    const remoteObj = await this.loadModal('/high_poly_building/test.obj')

    for (let i = 0; i < count; i++) {
      const x = (i % columns) * spacing - (columns - 1) * spacing * 0.5;
      const z = Math.floor(i / columns) * spacing - (rows - 1) * spacing * 0.5;
      const position = new THREE.Vector3(x, 0, z);
      const scale = .1;
      const e = remoteObj.clone()
      e.scale.set(scale, scale, scale);
      e.position.copy(position);

      const mesh = e.children[0]
      mesh.userData = data[i]
      mesh.material[0].color = new THREE.Color(this.meshParams.light.color)
      mesh.material[0].emissive = new THREE.Color(this.meshParams.light.emissive)
      mesh.material[1].color = new THREE.Color(this.meshParams.shade.color)
      mesh.material[1].emissive = new THREE.Color(this.meshParams.shade.emissive)
      mesh.material = mesh.material.map(v => {
        v.side = THREE.DoubleSide;
        return v.clone();
      })

      this.scene.add(e);
      this.models.push(e)

      mesh.on('click', (e) => {
        click && click({ e, mesh })
      })
      // 添加鼠标移入事件
      mesh.on('mouseover', (e) => {
        // 鼠标移入时，将模型颜色设置为橙色
        const color = new THREE.Color(this.meshParams.hoverColor)
        const emissive = new THREE.Color(this.meshParams.hoverEmissive)
        mesh.material.forEach(material => {
          material.color = color
          material.emissive = emissive
        })
        mousemove && mousemove({ e, mesh })
      });
      // 添加鼠标移出事件
      mesh.on('mouseout', (e) => {
        // 鼠标移出时，将模型颜色恢复为原始颜色
        mesh.material[0].color = new THREE.Color(this.meshParams.light.color)
        mesh.material[0].emissive = new THREE.Color(this.meshParams.light.emissive)
        mesh.material[1].color = new THREE.Color(this.meshParams.shade.color)
        mesh.material[1].emissive = new THREE.Color(this.meshParams.shade.emissive)
        mouseout && mouseout({ e, mesh })
      });
    }
    this.adjustCameraToFitModels()
  }

  // 加载外部模型
  loadModal(url, mtl = null) {
    if (process.env.NODE_ENV === 'development') {
      console.log('开发环境');
    } else {
      console.log('生产环境');
      url = '/xibiketest' + url
    }
    //本地模型不显示，需注释
    // url = '/xibiketest' + url
    return new Promise((reslove, reject) => {
      const objLoader = new OBJLoader();
      if (mtl)
        objLoader.setMaterials(mtl)
      objLoader.load(url, (e) => {
        reslove(e)
      }, (xhr) => {
        // console.log((xhr.loaded / xhr.total * 100) + '% loaded');
      });
    })
  }

  // 居中显示
  adjustCameraToFitModels() {
    const box = new THREE.Box3();
    for (const model of this.models) {
      box.expandByObject(model);
    }

    const modelSize = new THREE.Vector3();
    box.getSize(modelSize)
    const modelCenter = new THREE.Vector3();
    box.getCenter(modelCenter);

    // 计算相机距离模型包围盒的最大边的距离，确保所有模型在画布内
    const maxModelSize = Math.max(modelSize.x, modelSize.y, modelSize.z);
    const distance = maxModelSize * 2;
    // 设置相机位置和观察目标点
    this.camera.position.set(
      modelCenter.x + distance * Math.cos(THREE.MathUtils.degToRad(76)),
      modelCenter.y + distance * Math.tan(THREE.MathUtils.degToRad(28)),
      modelCenter.z + distance * Math.sin(THREE.MathUtils.degToRad(76))
    );
    this.camera.lookAt(modelCenter);
    this.cameraPosition = this.camera.position.clone()

    // 根据屏幕纵横比动态调整 FOV，确保所有模型在画布内并保留边距
    const aspect = window.innerWidth / window.innerHeight;
    const padding = maxModelSize * 0.2; // 设置留白大小
    const fov = THREE.MathUtils.radToDeg(2 * Math.atan((modelSize.y + 2 * padding) / (2 * distance))) * aspect;
    this.camera.fov = fov * 0.8;
    // this.camera.fov = 32.66621039374138;
    this.camera.updateProjectionMatrix();
  }

  // 检测新模型的碰撞
  checkCollisions(boxs, newModel) {
    const box = new THREE.Box3().setFromObject(newModel);
    return boxs.some(existingModels => {
      return box.intersectsBox(existingModels)
    })
  }
  // 加载背景
  async loadeBackBuild() {
    this.backModels = []
    // 加载模型

    const obj = await this.loadModal('/high_poly_building/buildings.obj')
    this.backModels = [...obj.children].map((model) => {
      const scale = .005;
      model.scale.set(scale, scale, scale);
      model.position.set(0, -14, 0);
      return model;
    });
    this.group = new THREE.Object3D();
    const gridSize = 30
    const spacing = 2
    const max = .002;
    const min = .0001;

    const boxs = this.models.map(v => {
      return new THREE.Box3().setFromObject(v);
    })

    const material = new THREE.MeshPhysicalMaterial(this.backMeshParams);

    for (let i = 0; i < gridSize * gridSize; i++) {
      // 获取随机一个建筑
      const building = this.backModels[Math.floor(Math.random() * Math.floor(this.backModels.length))].clone();
      building.material = material;
      building.scale.y = Math.random() * (max - min + .01);
      building.position.x = (i % gridSize) * spacing - gridSize;
      building.position.y = 0;
      building.position.z = Math.floor(i / gridSize) * spacing - gridSize - 10;
      if (!this.checkCollisions(boxs, building)) {
        this.group.add(building);
      }
    }

    this.scene.add(this.group);
  }

  // 鼠标移动
  tilt() {
    const lerp = (a, b, n) => (1 - n) * a + n * b;
    const lineEq = (y2, y1, x2, x1, currentVal) => {
      let m = (y2 - y1) / (x2 - x1);
      let b = y1 - m * x1;

      return m * currentVal + b;
    };
    this.lastMouseX = lerp(this.lastMouseX, lineEq(this.cameraPosition.x - 1.5, this.cameraPosition.x + 1.5, this.width, 0, this.mouseX), 0.1);
    this.lastMouseY = lerp(this.lastMouseY, lineEq(this.cameraPosition.y - 0.8, this.cameraPosition.y + 0.8, this.height, 0, this.mouseY), 0.1);
    this.camera.position.x = this.lastMouseX
    this.camera.position.y = this.lastMouseY
  }
  // 渲染
  animate() {
    if (this.stats) this.stats.update()
    this.tilt()
    this.renderer.render(this.scene, this.camera); //执行渲染操作
    this.animationId = requestAnimationFrame(this.animate.bind(this));//请求再次执行渲染函数render，渲染下一帧
  }

  destoryResource() {
    try {
      this.offResize();
      this.offMouse()
      if (this.scene) {
        this.scene.clear()
        this.scene = null
      }
      if (this.camera)
        this.camera = null;
      if (this.renderer) {
        this.renderer.dispose()
        this.renderer.forceContextLoss()
        this.dom.removeChild(this.renderer.domElement)
      }
      cancelAnimationFrame(this.animationId)
    } catch (error) {
      console.log(error, 'error');
      // debugger;
    }
  }
}


let build
export default {
  props: {
    data: {
      type: Array,
      default: () => ([{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}])
    }
  },
  watch: {
    data: {
      deep: true,
      handler() {
        this.init()
        // console.log("watch渲染");
      }
    }
  },
  mounted() {
    // this.init()
    this.$once('hook:beforeDestroy', () => {
      build && build.destoryResource()
    })
  },
  methods: {
    init() {
      // if (this.data.length < 0) return
      build && build.destoryResource()
      build = new Buiding()
      build.init({
        dom: this.$refs.container,
        data: this.data,
        click: args => this.$emit('buildClick', args),
        mousemove: args => this.$emit('buildMousemove', args),
        mouseout: args => this.$emit('buildMouseout', args),
      })
    }
  }
}
</script>

<style lang="scss" scoped>
.container {
  width: 100%;
  // height: calc(100vh - 100px);
  height: 100%;
}




// loading
.loader {
  position: fixed;
  z-index: 10000;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  fill: red;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}

.loader--done {
  opacity: 0;
  transition: opacity 1.4s ease-out;
}

.loader__icon {
  width: 70px;
  height: 70px;
}

.loader__icon g {
  -webkit-animation: showHide 0.8s steps(4, end) infinite forwards;
  animation: showHide 0.8s steps(4, end) infinite forwards;
}

.loader__icon g:last-child {
  -webkit-animation-delay: 0.5s;
  animation-delay: 0.5s;
}

@-webkit-keyframes showHide {
  to {
    opacity: 0;
  }
}

@keyframes showHide {
  to {
    opacity: 0;
  }
}

.loader--done .loader__icon g {
  opacity: 0;
  transition: opacity 0.5s;
}
</style>
